﻿#Requires AutoHotkey v2.0
#SingleInstance Force

#Include g:\AHK\git-ahk-lib\Extend.ahk
#Include g:\AHK\git-ahk-lib\Path.ahk

table := Map(
  'C0', '0',
  'C0#', '1',
  'D0-', '1',
  'D0', '2',
  'D0#', '3',
  'E0-', '3',
  'E0', '4',
  'F0', '5',
  'F0#', '6',
  'G0-', '6',
  'G0', '7',
  'G0#', '8',
  'A0-', '8',
  'A0', '9',
  'A0#', '10',
  'B0-', '10',
  'B0', '11',
  'C1', '12',
  'C1#', '13',
  'D1-', '13',
  'D1', '14',
  'D1#', '15',
  'E1-', '15',
  'E1', '16',
  'F1', '17',
  'F1#', '18',
  'G1-', '18',
  'G1', '19',
  'G1#', '20',
  'A1-', '20',
  'A1', '21',
  'A1#', '22',
  'B1-', '22',
  'B1', '23',
  'C2', '24',
  'C2#', '25',
  'D2-', '25',
  'D2', '26',
  'D2#', '27',
  'E2-', '27',
  'E2', '28',
  'F2', '29',
  'F2#', '30',
  'G2-', '30',
  'G2', '31',
  'G2#', '32',
  'A2-', '32',
  'A2', '33',
  'A2#', '34',
  'B2-', '34',
  'B2', '35',
  'C3', '36',
  'C3#', '37',
  'D3-', '37',
  'D3', '38',
  'D3#', '39',
  'E3-', '39',
  'E3', '40',
  'F3', '41',
  'F3#', '42',
  'G3-', '42',
  'G3', '43',
  'G3#', '44',
  'A3-', '44',
  'A3', '45',
  'A3#', '46',
  'B3-', '46',
  'B3', '47',
  'C4', '48',
  'C4#', '49',
  'D4-', '49',
  'D4', '50',
  'D4#', '51',
  'E4-', '51',
  'E4', '52',
  'F4', '53',
  'F4#', '54',
  'G4-', '54',
  'G4', '55',
  'G4#', '56',
  'A4-', '56',
  'A4', '57',
  'A4#', '58',
  'B4-', '58',
  'B4', '59',
  'C5', '60',
  'C5#', '61',
  'D5-', '61',
  'D5', '62',
  'D5#', '63',
  'E5-', '63',
  'E5', '64',
  'F5', '65',
  'F5#', '66',
  'G5-', '66',
  'G5', '67',
  'G5#', '68',
  'A5-', '68',
  'A5', '69',
  'A5#', '70',
  'B5-', '70',
  'B5', '71',
  'C6', '72',
  'C6#', '73',
  'D6-', '73',
  'D6', '74',
  'D6#', '75',
  'E6-', '75',
  'E6', '76',
  'F6', '77',
  'F6#', '78',
  'G6-', '78',
  'G6', '79',
  'G6#', '80',
  'A6-', '80',
  'A6', '81',
  'A6#', '82',
  'B6-', '82',
  'B6', '83',
  'C7', '84',
  'C7#', '85',
  'D7-', '85',
  'D7', '86',
  'D7#', '87',
  'E7-', '87',
  'E7', '88',
  'F7', '89',
  'F7#', '90',
  'G7-', '90',
  'G7', '91',
  'G7#', '92',
  'A7-', '92',
  'A7', '93',
  'A7#', '94',
  'B7-', '94',
  'B7', '95',
  'C8', '96',
  'C8#', '97',
  'D8-', '97',
  'D8', '98',
  'D8#', '99',
  'E8-', '99',
  'E8', '100',
  'F8', '101',
  'F8#', '102',
  'G8-', '102',
  'G8', '103',
  'G8#', '104',
  'A8-', '104',
  'A8', '105',
  'A8#', '106',
  'B8-', '106',
  'B8', '107',
  'C9', '108',
  'C9#', '109',
  'D9-', '109',
  'D9', '110',
  'D9#', '111',
  'E9-', '111',
  'E9', '112',
  'F9', '113',
  'F9#', '114',
  'G9-', '114',
  'G9', '115',
  'G9#', '116',
  'A9-', '116',
  'A9', '117',
  'A9#', '118',
  'B9-', '118',
  'B9', '119',
  'C10', '120',
  'C10#', '121',
  'D10-', '121',
  'D10', '122',
  'D10#', '123',
  'E10-', '123',
  'E10', '124',
  'F10', '125',
  'F10#', '126',
  'G10-', '126',
  'G10', '127',
  'C', '60',
  'C#', '61',
  'D-', '61',
  'D', '62',
  'D#', '63',
  'E-', '63',
  'E', '64',
  'F', '65',
  'F#', '66',
  'G-', '66',
  'G', '67',
  'G#', '68',
  'A-', '68',
  'A', '69',
  'A#', '70',
  'B-', '70',
  'B', '71',
)

instrument := Map(
  '43', 'Electric Piano',
  '41', 'Grand Piano',
  '17', 'Harpsichord',
  '25', 'Ragtime Piano',
  '26', 'Music Box',
  '0', 'Ele. Piano (Classic)',
  '8', 'Grand Piano (Classic)',
  '2', 'Drum Kit',
  '31', 'Electric Drum Kit',
  '19', 'Xylophone',
  '34', 'Vibraphone',
  '21', 'Steel Drums',
  '39', '8-Bit Drum Kit',
  '40', '2013 Drum Kit',
  '36', '808 Drum Kit',
  '42', '909 Drum Kit',
  '53', '2023 Drum Kit',
  '60', 'EDM Kit (E)',
  '58', 'Acoustic Guitar',
  '4', 'Electric Guitar (Bass)',
  '30004', 'Reverse Electric Guitar',
  '40004', 'Electric Guitar high',
  '48', 'Bass',
  '54', 'Bass Guitar',
  '29', 'Slap Bass',
  '32', 'Jazz Guitar',
  '35', 'Muted E-Guitar',
  '44', 'Distortion Guitar',
  '20044', 'Distortion Guitar right',
  '49', 'Clean Guitar',
  '22', 'Sitar',
  '33', 'Koto',
  '1', 'Acoustic Gtr (Classic)',
  '5', 'Bass Guitar (Classic)',
  '38', 'Dist. Guitar (Classic)',
  '55', 'Synthesizer',
  '52', 'Smooth Synth',
  '6', 'Synth Pluck',
  '7', 'Scifi',
  '13', 'Kasane Teto Simple',
  '10013', 'Oh Mah Gah',
  '20013', 'Aya Ready?',
  '14', '8-Bit Square',
  '10014', '8-Bit Square 2',
  '15', '8-Bit Sawtooth',
  '10015', '8-Bit Sawtooth 2',
  '16', '8-Bit Triangle',
  '57', 'Pop Synth',
  '10057', 'Pop Synth right pan',
  '56', 'Synth Bass',
  '37', '808 Bass',
  '30', 'Pop Synth (Classic)',
  '27', 'Synth Bass (Classic)',
  '50', 'French Horn',
  '10050', 'By SprootRC',
  '51', 'Trombone',
  '61', 'Brass',
  '46', 'Violin',
  '45', 'Cello',
  '18', 'Concert Harp',
  '20', 'Pizzicato',
  '23', 'Flute',
  '47', 'Strings',
  '24', 'Saxophone',
  '28', 'Church Organ',
  '9', 'French Horn (Classic)',
  '10', 'Trombone (Classic)',
  '11', 'Violin (Classic)',
  '12', 'Cello (Classic)',
  '59', 'Lucent Choir',
)

timeline := []
ConvertGui().Show()

class ConvertGui extends Gui {

  f := ''

  __New() {
    super.__New()
    this.SetFont('s14', 'Consolas')
    this.AddText('', 'A tool for converting midi sequences to ahk codes.')
    this.AddEdit('vb w420 h30 -Multi', '150'), this.AddText('yp+3 x+30 h20', 'BPM')
    this.AddEdit('ve xm w420 h200 ReadOnly')
    this.AddButton('yp Section w80', 'Select').OnEvent('click', (*) => this.SelectFile())
    this.AddButton('w80', 'to v1').OnEvent('Click', (*) => this.Resolve(1))
    this.AddButton('w80', 'to v2').OnEvent('Click', (*) => this.Resolve(2))
    this.AddButton('w80', 'reset').OnEvent('Click', (*) => this.Reset())
    this.OnEvent('DropFiles', (g, ctrl, files, *) => this.OnDrop(files[1]))
  }

  OnDrop(file) {
    this.Log(file)
    this.f := file
  }

  SelectFile(*) {
    f := FileSelect(3, , 'Select a script file', '(*.txt)')
    if f {
      if this.f
        this.Log('Entry file has been changed: ' f)
      else
        this.Log('Entry file has been selected: ' f)
      this.f := f
    }
  }

  Resolve(ver) {
    if !this.f {
      this.Log('Select a file first.')
      return
    }
    Reset()
    this.Log('==========START==========')
    this.Log('Resolving file: ' this.f)
    Convert(this.f, this['b'].Value)
    targetPath := Path.Parse(this.f).nameNoExt '.ahk'
    if ver = 1
      ConvertToV1(timeline, './music-v1/' targetPath)
    else
      ConvertToV2(timeline, './music-v2/' targetPath)
    this.Log('===========END===========')
  }

  Reset() {
    Reset()
    this['e'].Value := ''
  }

  Log(t) => this['e'].Value .= '>> ' t '`n'
}


Convert(filepath, BPM) {
  data := FileRead(filepath, 'utf-8').split(';')
  data.pop()
  data[1] := RegExReplace(data[1], '.*:(.*)', '$1')

  TPB := 60 / BPM * 1000 / 4

  for l in data {
    if !l
      continue

    splited := StrSplit(l, A_Space), start := splited[1], ch := splited[2], duration := splited[3] * TPB

    if InStr(ch, '#')  ; 将 A#6 改成 A6#
      ch := RegExReplace(ch, '(.*)#(.*)', '$1$2#')

    tick := timeline[-1]
    if tick.start = start
      tick.ch.Push({ ch: ch, duration: duration })
    else {
      tick.delay := (start - tick.start) * TPB
      timeline.Push({ start: start, ch: [{ ch: ch, duration: duration }], delay: '0' })
    }
  }
}

Reset() {
  global timeline := [{ start: '0', ch: [], delay: '0' }]
}


ConvertToV1(timeline, filepath) {
  res := '#NoEnv`n'
  res .= '#persistent`n`n'
  res .= '#Include Music.ahk`n`n'
  res .= 'Notes := new NotePlayer`n`n'
  res .= 'Notes.Instrument(0)`n`n'
  for v in timeline {
    res .= 'Notes'
    res .= v.ch.reduce((a, b) => a . Format('.Note({}, {})', table.Get(b.ch), b.duration), '')
    res .= '.Delay(' v.delay ') `; ' A_Index '`n'
  }
  res .= '`nNotes.Start()`n'

  f := FileOpen(filepath, 'w', 'utf-8')
  f.Write(res)
  f.Close()
}


ConvertToV2(timeline, filepath) {
  res := '#Include MusicV2.ahk`n'
  res .= 'persistent`n`n'
  res .= 'Notes := NotePlayer()`n`n'
  res .= 'Notes.Instrument(0)`n`n'
  for v in timeline {
    res .= 'Notes'
    res .= v.ch.reduce((a, b) => a . Format('.Note({}, {})', table.Get(b.ch), b.duration), '')
    res .= '.Delay(' v.delay ') `; ' A_Index '`n'
  }
  res .= '`nNotes.Start()`n'

  f := FileOpen(filepath, 'w', 'utf-8')
  f.Write(res)
  f.Close()
}